RiverSync
SPEC-UX-COM · v1.0
28 June 2026
Owner: Platform team

Design specification · build-ready · Pipeline · Communications

The communications system — the unified inbox, specified to the pixel

pipeline.riversync.com / Communications. The exhaustive design specification for RiverSync's unified multi-channel inbox: layout grid, every panel and state, the data it renders, the realtime push that keeps it live, and the design-system mapping for every element. Written so an implementer needs no further decisions — each measurement, class, icon, enum and event is named.

Prototyped Design spec · v1.0 As-built + production targets
This is a design view over a settled model. The requirement is SPEC-APP-PIP PIP-15 (realizing platform SAL-9); the entities Conversation · Message and rules DM-53 · DM-54 live in the ERD; the events & service in the Sales domain (SVC-18); the process in SPEC-PWF-COM and the lifecycle in SPEC-DWF-SAL. This document owns no model — it specifies how that model is presented and operated. On any conflict the platform master wins. Prototype of record: apps/pipeline/Inbox.html + pipeline-inbox.js.

1What it is — and what it is not

The communications system is a single threaded inbox that centralizes every external touchpoint with a contact, across seven channels, into one place a sales user works without leaving Pipeline. Each conversation is one channel thread tied to the contact, customer or partner it came from, and to the leads, opportunities and deals that relationship drives — so a user moves in one click from a message to the relationship and its activity.

It is
  • The RiverSync-side sales inbox — outbound & inbound, RiverSync sales/admin only.
  • Channel-agnostic: one thread idiom for web form, email, LINE, Instagram, Facebook, LinkedIn and logged phone.
  • Relationship-anchored: every thread carries a ContactId and 0..1 funnel link.
  • An activity source: every send/receive writes an Activity onto the blended timeline (PIP-14).
It is not
  • Customer↔support messaging — that is ChatThread (Portal/Operations, CustomerId-scoped, alarm & appointment cards). Different system, different DS surface.
  • A marketing-automation suite — no sequences, no bulk send, no campaign builder.
  • A second source of truth for the relationship — the contact, lead, opportunity and deal records remain canonical; the inbox links to them.

2The shape at a glance

Seven inbound/outbound channels collapse into one queue, organized across six folders. The same single page renders every folder; the channel is the reply path, the folder is a soft, reversible placement.

7 channels web-formemaillineinstagramfacebooklinkedinphone
6 folders inboxdraftsentarchivedjunkdeleted

Entitlement: RiverSync tenant, sales or admin role (master AUTH-2; Pipeline is RiverSync-only). Support · accounting · engineer are not entitled and never see the app. The five W's of every conversation — who (contact), where from (channel), about what (subject + linked funnel record), when (last message), what state (folder + read) — are all visible in the list row before the thread is even opened.

RiverSync Co., Ltd. · BangkokSPEC-UX-COM · 1

3Information architecture & routing

Communications is the first nav group in the Pipeline sidebar, above Sales and Relationships. The six folders are six nav items; all six resolve to one page, Inbox.html, parameterized by a query string. There is no per-folder page.

FolderRouteNav idSemantics & allowed actions
Inbox?folder=inboxcm-inboxLive inbound queue across all seven channels, tied to the contact. Actions: reply · forward · forward all · archive · junk · mark unread · delete · convert.
Draft?folder=draftcm-draftUnsent replies & outreach. Primary action is Send; the composer pre-fills the draft body. Actions: continue editing · discard.
Sent?folder=sentcm-sentOutbound history, threaded with the contact. Read-only thread; reply opens a new outbound. Actions: reply · forward · forward all · archive · junk · delete.
Archived?folder=archivedcm-archivedResolved, out of the inbox, still attached to the relationship. No Archive command (already archived). Actions: reply · forward · forward all · junk · delete.
Junk?folder=junkcm-junkSpam/off-topic. Replies disabled — composer shows the junk lock. Primary action is Not junk (→ inbox). Actions: not junk · delete · empty junk.
Deleted?folder=deletedcm-deletedSoft-deleted, retained per policy (LIF-1). Replies disabled — restore first. Primary action is Restore (→ inbox). Actions: restore · delete forever · empty folder.
SoftFolders are soft states (DM-54). Junk and Deleted are reversible; Deleted is retained, never hard-purged from the UI (the only hard delete is the audit retention purge). A conversation has exactly one folder at a time; moving it emits conversation.foldered {ConversationId, Folder, By}.

3.1Entry points

3.2The page is immersive

Communications uses the shell's immersive page mode (pl-immersive on .rs-page, pl-immersive-body on <body>): the standard page padding and max-width are dropped so the inbox fills the content area edge to edge below the 40px top bar. The sidebar (248px, collapsible to 40px) and top bar are the standard shell chrome — the App pill is rs-portalbadge--pipeline, breadcrumbs read Communications › {Folder}.

RiverSync Co., Ltd. · BangkokSPEC-UX-COM · 2

4The data the inbox renders

Two entities, owned by the Sales service (SVC-18), defined once in SPEC-ERD-PIP. The UI never invents fields — every rendered value maps to one below. Field dictionaries are reproduced here verbatim from the ERD catalog so an implementer reads layout and schema together.

Conversationcomms thread · CNV-YYYY-NNNN
FieldKeyMeaning & UI use
IdPKCNV-YYYY-NNNN — list-row data-id, reader anchor.
ContactIdFK→ Contact (party). Drives the avatar, name, org, role and the Open contact / View activity buttons.
Channelweb-formemaillineinstagramfacebooklinkedinphone — the channel chip + the reply path (DM-53).
Folderinboxdraftsentarchivedjunkdeleted — which queue; the soft state (DM-54).
SubjectThread title — list row line 3 and the reader subject bar.
Stateopenclosed — a closed conversation locks the composer.
RelatesToTypeleadopportunitydealquotenone — kind of the linked funnel record.
RelatesToIdFK→ funnel entity · 0..1. Renders the Linked chip row in the reader.
OwnerUserFK→ the RiverSync sales user who owns the thread.
LastMessageAt+ Unread — list timestamp and the unread dot / bold weight.
Messageper conversation · (ConversationId, SeqNo)
FieldKeyMeaning & UI use
ConversationIdPFParent thread.
SeqNoPFOrder within the thread — render ascending, top to bottom.
Directioninboundoutbound — inbound = left bubble, outbound = right bubble (pl-msg--out) (DM-54).
FromContactIdFK→ Contact · 0..1, inbound only. Outbound author is the OwnerUser.
BodyMarkdown / text — rendered injection-safe; logged calls carry an italic note body.
SentAtPer-message timestamp in the bubble head.
DeliveryStatedraftsentdeliveredread — the outbound delivery tick (§10.4).
Attachmentsfiles · pictures · 0..n — each renders an attachment chip with name, size, kind, download.
DM-53Conversation is the unified communications thread. One thread per topic, anchored to a ContactId, carrying its Channel; may relate to one funnel entity. Distinct from ChatThread (support) and Activity (the touchpoint log) — but each Message writes an Activity so the blended journey timeline (DM-52) surfaces it.
DM-54Folders are soft states; channel is the reply path. A Message belongs by (ConversationId, SeqNo) with its Direction and DeliveryState. Folder is a soft placement — junk & deleted reversible, deleted retained (LIF-1); a reply or forward goes out on the conversation's own Channel.
RiverSync Co., Ltd. · BangkokSPEC-UX-COM · 3

5Layout & grid

Below the shell chrome the page is a vertical stack: a full-width command bar, then a two-column mail body that fills the remaining height. The mail body is a CSS grid — a fixed-width conversation list on the left, the reader filling the rest. Both columns scroll independently; the page itself does not scroll.

Reply Compose Search messages… Linked · LEAD-198 Reply on LINE Send 1 2 3 4 5 6 7
Communications page anatomy (reference layout, not to absolute scale). Numbered regions are specified in the table below.
RegionSizingSpecification
1Command bar .pl-commsbarfull width · 46pxSticky top of the immersive page. Left: context commands (icon buttons). Right cluster: primary context action + Compose. A flex spacer separates them.
2List column .pl-mail__listfixed 320px*Conversation list; scrolls independently. *Prototype renders a fluid ~38% column; production target is a fixed 320px list with the reader taking the remainder.
3List tools .pl-mail__toolsauto · ~44pxSearch input (fills) + filter button (34px). Pinned above the scrolling items.
4Reader column .pl-mail__readerfillsTakes all remaining width; internal vertical layout: head → subject → thread (scrolls) → composer (pinned bottom).
5Linked chips .pl-reader__relsautoThe conversation's funnel links (lead/opp/deal/partner) as click-through chips; hidden when none.
6Thread .pl-reader__threadfills · scrollsMessages ascending; inbound left, outbound right. Auto-scrolls to newest on open / send.
7Composer .pl-composerauto · pinnedChannel-aware reply box; replaced by a locked notice in junk/deleted/closed.

Grid: .pl-mail{display:grid; grid-template-columns:320px 1fr; height:calc(100% - 46px);}. Both panels min-height:0; overflow:auto so they scroll inside the grid rather than growing the page. Below 900px the list collapses to a full-width queue and selecting a conversation pushes the reader over it (master-detail), with a back affordance — see §13 responsive.

RiverSync Co., Ltd. · BangkokSPEC-UX-COM · 4

6The command bar

The bar is context-aware: which commands appear depends on the open folder and whether a conversation is selected. Secondary actions are icon-only buttons (.pl-cmd.pl-cmd--icon) on the left; the primary context action and Compose sit together at the right edge, sharing the filled accent style. When nothing is selected, the left side shows the hint "Select a conversation to act on it."

FolderLeft (secondary, icon-only)Right primary
InboxForward · Forward all · Archive · Mark as junk · Mark unread · DeleteReply (icon) + Compose
SentForward · Forward all · Archive · Mark as junk · Mark unread · DeleteReply (icon) + Compose
ArchivedForward · Forward all · Mark as junk · Mark unread · Delete (no Archive)Reply (icon) + Compose
DraftContinue editing · Discard draftSend + Compose
JunkDelete · Empty junkNot junk + Compose
DeletedDelete forever · Empty folderRestore + Compose

The primary action's act resolves from folder: junk → notjunk, deleted → restore, draft → send, else → reply. Reply is rendered icon-only (corner-down-left) to sit tight beside Compose; the others carry a label. Compose is always present and always last.

CommandIconStyleBehavior
Replycorner-down-leftprimaryFocuses the composer and scrolls the thread to its end. No toast.
Forwardcorner-down-righticonOpens an outbound compose forwarding the conversation on its own channel; writes an Activity; emits conversation.forwarded {Scope:conversation}.
Forward allforward-alliconForwards the entire thread — every Message — on the same channel; conversation.forwarded {Scope:thread}.
ArchivearchiveiconMoves to Archived; toast "Moved out of the inbox — still attached to the relationship."
Mark as junkshieldiconMoves to Junk; conversation.foldered {Folder:junk}.
Mark unreademailiconRe-flags the selected row unread (dot + bold) without leaving it.
Deletetrashicon · dangerMoves to Deleted (retained). In Deleted the label is Delete forever.
Not junkinboxprimaryJunk only — returns to Inbox.
RestorerefreshprimaryDeleted only — returns to Inbox.
SendsendprimaryDraft only — sends the draft on its channel; moves the conversation to Sent.
Empty junk / foldertrashdangerClears the whole folder (retained per policy); always present in junk/deleted regardless of selection.
ComposesendprimaryOpens the new-message modal (§9). Always present, far right.
No refreshThere is no manual Refresh command — the inbox is realtime (PIP-15 v0.9). Removed in favor of the SignalR push (§11). Do not reintroduce a refresh button.
RiverSync Co., Ltd. · BangkokSPEC-UX-COM · 5

7The conversation list

The left column: a pinned tools row (search + filter) over a scrolling stack of conversation rows. The list is the queue; one row is selected at a time and reflected in the reader.

7.1Search & filter

ControlSpecification
Search #mailSearchLeading search icon, placeholder "Search messages…". Filters on input (no debounce needed at this scale) across party name, org, subject and preview — case-insensitive substring. Empties restore the full list.
Filter button #filterBtn34px square icon button (filter glyph). Opens a dropdown of facets. When any facet is active it gains is-active and a count badge (.pl-mail__filterbadge) showing the number of active facets.

The filter menu carries four single-select facets; each is a radio group (a tick marks the active option). Facets only appear when meaningful for the folder's contents.

FacetOptionsNotes
ChannelAll + each present channelShown only if the folder holds >1 channel; options carry the channel icon.
StatusAll · Unread · ReadAlways present.
AttachmentsAny · Has attachment"Has attachment" carries the paperclip icon.
RelationshipAnyone + present kindsLead · Customer · Partner · Unverified — shown only if the folder holds >1 kind.

Facets are AND-combined with search. A Clear all filters footer appears when any facet is active and resets all four to "all". The list header shows N shown (and of M when filtered). The filter dropdown closes on outside-click or Escape.

7.2Conversation row anatomy

One row (.pl-conv, a <button>) packs the whole at-a-glance summary into ~70px:

ElementSpec
Unread dot.pl-conv__dot — accent dot, left gutter; present only when unread. Row also gets is-unread (name bold).
Avatar34px .rs-av with the contact's initials and assigned color class (rs-av-c0…c5).
Name + channel + timeTop line: contact name (left), then the channel chip + relative time (right). Channel chip = rs-badge + tone (§9).
Org + clipSecond line: organization name, plus a paperclip glyph when the thread has attachments.
SubjectThird line: .pl-conv__subj — the conversation subject, medium weight.
PreviewFourth line: one-line snippet of the latest message, truncated; .pl-conv__prev, muted.
Selectedis-sel — accent-soft fill, persists while its thread is open.
SelectClicking a row selects it, marks it read (clears the dot, removes is-unread), and renders the thread. The header count and the sidebar unread pill update immediately. Selection survives re-filtering when the row is still present; otherwise the first visible row is selected.
RiverSync Co., Ltd. · BangkokSPEC-UX-COM · 6

8The reader

The right column is a fixed vertical layout: a contact head, a subject bar with linked chips, the scrolling thread, and a pinned composer (or lock).

RegionSpecification
Head .pl-reader__headAvatar (34px) + name with a kind tag (Lead / Customer / Partner / Unverified, .pl-ktag--*) + role · org. Right: Open contact and View activity ghost buttons (suppressed for unverified senders), then an overflow more-horizontal menu.
Overflow menuArchive (or Not junk / Restore by folder) · Convert to lead / opportunity… · separator · Delete (danger). Mirrors the command bar for reachability.
Subject bar .pl-reader__subjThe conversation subject (large) with the channel chip to its right.
Linked chips .pl-reader__relsLabel Linked + one chip per related record. Chip = icon by type (deal trend-up, opportunity layers, lead inbox, partner hub) + label + mono id; links to that record's page. Whole row hidden when none.
Thread .pl-reader__threadMessage blocks ascending. Inbound = left, avatar + body. Outbound = pl-msg--out, right-aligned, with a You tag. Each block: author, timestamp, markdown body, then attachment chips.
Attachment chip .pl-attKind icon (image / file-text), name (bold) + size, and a download affordance. Images open a lightbox; files download.

8.1The composer — channel-aware

The composer always sends on the conversation's own channel — there is no channel switch inside a thread. Its header states the reply verb per channel; its footer states the destination.

PartSpec
Bar.pl-composer__bar — channel icon + reply verb (e.g. "Reply on LINE", "Reply by email", phone → "Log a follow-up call") on the left; Attach file (paperclip) and Add picture (image) icon buttons on the right.
Textarea.pl-composer__ta — placeholder "Write a reply…" (or "Pick up your draft…" in Draft, where it pre-fills the draft body).
FooterMuted note "Sends on {Channel}, threaded to {Name}." + the Send button (Send draft in Draft).
Junk lock

.pl-reader__locked with the shield glyph — "This conversation is filtered as junk. Replies are disabled — mark it not junk to move it back to the inbox."

Deleted lock

trash glyph — "This conversation is in Deleted. Restore it to reply."

Closed thread

State = closed → the composer is replaced by the locked note; no outbound until reopened.

Read-only folders

Sent / Archived render the thread read-only but still offer a reply (which opens a fresh outbound on the same channel).

8.2Empty reader

When the list is empty (no conversations, or none match the filter) the reader shows .pl-reader__empty: the folder's icon, "Select a conversation", and the folder's descriptive subtitle. The list itself shows .pl-mail__none — a search glyph + "No conversations match." when filtered to nothing.

RiverSync Co., Ltd. · BangkokSPEC-UX-COM · 7

9The compose modal

Compose opens a large modal (.rs-modal.rs-modal--lg in an .rs-scrim--drawer) for net-new outreach — distinct from a thread reply, because here the recipient and channel are not yet fixed.

FieldSpec
HeaderSend-icon tile (accent-soft) + "New message" + sub "Reach a contact on any channel. The thread attaches to their relationship and activity." + close.
To (required)Contact / customer / partner picker (leading user icon). Resolves to a ContactId — the thread anchors there.
Channel (required)Select: Email — sales@riversync.com · LINE · Instagram · Facebook · LinkedIn · Web form reply. (Phone threads are logged calls, created from the call-log flow, not composed here.)
SubjectFree text — "What is this about?"
MessageMulti-line body — "Write your message…"
ReassuranceAn info alert: "One thread per topic" — whichever channel, the conversation centralizes here and links to the contact.
FooterSave as draft (ghost → Draft folder) · Send (primary). Send closes the modal and toasts "It's threaded to the contact and added to their activity timeline."
ResultA successful Send creates a Conversation (folder sent, anchored to the resolved contact) + its first outbound Message, writes an Activity, and emits message.sent. Save-as-draft creates the Conversation in folder draft with a draft-state Message and no event beyond the local create.
RiverSync Co., Ltd. · BangkokSPEC-UX-COM · 8

10The seven channels

One thread idiom serves all seven; only the channel chip, the reply verb and a few behaviors differ. The chip is the DS Badge (rs-badge + tone) with the channel icon + label — never a hand-rolled pill.

ChannelIconBadge toneReply verbNotes
Web formglobeneutralReply by emailThe riversync.com contact form — the only fully self-service capture. Reply routes to the submitter's email.
Emailemailrs-badge--infoReply by emailTo sales@riversync.com. Threading by subject + participant.
LINEliners-badge--successReply on LINEThailand's primary channel — expect high inbound volume.
Instagraminstagramrs-badge--accentReply on InstagramDMs from the brand account; previews show @handle.
Facebookfacebookrs-badge--infoReply on FacebookPage messages.
LinkedInlinkedinrs-badge--infoReply on LinkedInInMail / connection messages; B2B intros.
Phonephoners-badge--successLog a follow-up callLogged calls — outbound-authored by the owner; body is an italic call note with duration. No live inbound push.
RuleChannel is fixed per conversation. A reply or forward always goes out on the conversation's own channel — there is no in-thread channel switch. To reach the same contact on a different channel, Compose a new conversation. This keeps each thread a faithful single-channel record (DM-54).

Inbound capture is the integration boundary, not a UI concern: each provider's webhook/poller lands a Message and emits message.received (§11). The UI treats all seven identically once a Message exists — it never speaks a provider API directly.

RiverSync Co., Ltd. · BangkokSPEC-UX-COM · 9

11Interactions & states

Every action runs through one dispatcher keyed by an act string, fired from three places — the command bar, the composer Send, and the reader overflow menu — so a given action behaves identically wherever it is invoked.

actResultToast
replyFocus composer, scroll thread to end— (no toast)
sendAppend outbound Message; → Sent; write Activity"Sent · Message sent on its channel and added to the contact's activity." (success)
forwardOpen outbound forward (conversation)"Forward · Opens a forward of this conversation on the same channel." (info)
forwardallOpen outbound forward (whole thread)"Forward all · …the entire thread — every message…" (info)
archive→ Archived"Archived · Moved out of the inbox — still attached to the relationship." (info)
junk→ Junk"Marked as junk · …Mark it not junk to bring it back." (warning)
notjunk→ Inbox"Moved to inbox · Marked not junk…" (success)
restore→ Inbox"Restored · Conversation moved back to the inbox." (info)
delete→ Deleted (retained)"Deleted · Moved to Deleted, retained per policy." (warning)
unreadRe-flag selected row unread"Marked unread · Flagged as unread in the list." (info)
emptyClear the whole folder (retained)"Folder emptied · …(retained per policy)." (warning)
convertOpen lead/opportunity capture, pre-filled"Convert · Opens the lead / opportunity capture, pre-filled…" (info)

11.1Folder-move model

Each move sets the conversation's Folder and emits conversation.foldered {ConversationId, Folder, By}. The moved conversation leaves the current list immediately; selection advances to the next visible row (or empties the reader if none remain). Reversible moves (junk ⇄ inbox, deleted → inbox) are the same mechanism in reverse.

inbox → archived inbox ⇄ junk any → deleted deleted → inbox draft → sent

11.2Read / unread & counts

Selecting a row clears Unread; act:unread sets it again. The sidebar folder pill and the list header count derive from the unread set and update synchronously on any change. Unread rows carry the dot and bold name; read rows do not.

11.3Load, empty & error states

Loading

List and reader reserve their exact dimensions and show skeleton rows / a quiet placeholder — layout never reflows when data arrives (mirrors the charts rule).

Empty folder

List → folder icon + the folder's subtitle. Reader → "Select a conversation" empty state. No error styling — an empty Junk is success.

No filter match

List → search glyph + "No conversations match."; Clear-all in the filter footer resets.

Send / load error

One-sentence inline error + Retry; an optimistic outbound that fails its server ack rolls back its DeliveryState to a retryable error chip (§11.4, §12).

11.4Delivery states

Outbound messages carry a DeliveryState that advances draftsentdeliveredread. Render a tick that fills as it advances (single → double → accent double on read), mono timestamp alongside. A failed send shows a danger state with Retry; it never silently disappears.

RiverSync Co., Ltd. · BangkokSPEC-UX-COM · 10

12Realtime contract

The inbox is live (PIP-15 v0.9): new inbound messages, delivery-state changes and folder moves are pushed to the client over a SignalR (WebSocket) hub and applied in place — no manual refresh. Local user actions update optimistically and reconcile on the server acknowledgement. The push rides the existing Sales domain events; the hub is the transport, not a new source of truth.

12.1Hub & groups

ConceptSpec
Hub/hubs/communications on the Pipeline BFF. Authenticated as the RiverSync sales/admin user; rejects unentitled roles (SVC-3 — browser talks only to its app's BFF).
GroupEach connection joins the group of the RiverSync sales tenant (and optionally per-owner sub-groups) so a push reaches every open Pipeline inbox. Conversations are RiverSync-internal — no customer/partner ever joins this hub.
ScopeThe hub carries Sales communications only. Customer↔support realtime is a separate concern on the Portal/Operations side (ChatThread) — never multiplexed here.

12.2Server → client methods

MethodPayload (from event)Client applies
MessageReceived{ConversationId, MessageId, ContactId, Channel, Folder}Append the message; if the thread is open, render & auto-scroll; else raise the row's unread dot, re-sort by LastMessageAt, bump counts, toast + bell badge.
MessageSent{ConversationId, MessageId, ContactId, Channel}Reconcile an optimistic outbound (replace temp id with server id) or append if it originated elsewhere.
DeliveryChanged{ConversationId, MessageId, DeliveryState}Advance the delivery tick in place; no reflow.
ConversationFoldered{ConversationId, Folder, By}If the conversation left the current folder, remove its row & advance selection; if it arrived, insert it.
ConversationForwarded{ConversationId, ContactId, Channel, Scope, MessageId}Append the forwarded outbound to the thread & the contact's activity.

12.3Client → server & optimistic reconcile

1
clientUser sends a reply. The UI optimistically appends the outbound Message with a temp id and DeliveryState=sending, focuses-out, and clears the composer.
2
client→BFFPOST /sales/conversations/{id}/messages (or the forward route). The BFF persists, emits message.sent, returns the canonical Message.
3
serverOn ack, the client reconciles: temp id → server id, DeliveryState=sent. The hub also broadcasts MessageSent to other open inboxes.
4
serverProvider delivery/read receipts arrive later as DeliveryChanged pushes and advance the tick.
failureIf the POST fails, roll the optimistic message back to a danger state with Retry — never drop it silently.
ConcernRule
OrderingServer SeqNo is authoritative. Optimistic messages render last until reconciled, then snap to their SeqNo position.
IdempotencyA push for a Message already present (matched by id) is a no-op — guards against the ack + broadcast double-apply.
ReconnectionOn reconnect, re-fetch the active folder + open thread to backfill anything missed while offline; the hub does not replay.
Reduced motionNew-message insert and delivery-tick changes are instant (no slide) under prefers-reduced-motion.
RiverSync Co., Ltd. · BangkokSPEC-UX-COM · 11

13Activity logging & cross-links

The inbox is not a silo. Every message keeps the relationship's history whole, and every thread is one click from the records it concerns.

A-1

Every send and receive writes an Activity. An inbound message.received and an outbound message.sent each append an Activity (type email/call/…, the matching direction) anchored to the conversation's ContactId — so the contact's blended timeline (PIP-14, DM-52) holds the full story without the user logging anything by hand.

A-2

Linked funnel records are first-class. A conversation's RelatesToType + RelatesToId render the Linked chip row; each chip deep-links to the lead, opportunity, deal or partner. The head's Open contact and View activity jump to the contact record and its timeline.

A-3

Convert in place. The reader overflow offers Convert to lead / opportunity…, pre-filled from the conversation — turning an inbound message into a tracked pursuit without retyping (feeds Lead to opportunity).

DistinctActivity is the human relationship log (owner-editable, audited); AuditEvent is the immutable system fact; ChatThread is customer↔support messaging. The inbox writes Activity and links funnel records — it does not duplicate them.

14Accessibility, keyboard & motion

ConcernRequirement
SemanticsList is a role="listbox" of rows (each a <button>, aria-selected); reader is the labelled detail region. The unread dot has an aria-label="unread"; the channel chip's label is real text, not icon-only.
Focus3px soft focus ring (--ring) on :focus-visible only — rows, buttons, composer, filter options. Opening a thread moves focus to the reader head; closing the filter returns focus to the filter button.
Filter menuRoving radio semantics per facet (role="radiogroup"); Escape closes and restores focus; outside-click closes.
ColorChannel/relationship meaning is never color-only — each chip pairs an icon + text label; delivery state pairs the tick with a tooltip word.
MotionToasts, new-message inserts and delivery ticks are instant under prefers-reduced-motion; no decorative looping.
Hit targetsRows ≥ the compact 38px row; icon buttons 34px; composer Send comfortably tappable.
KeyAction (proposed for production; prototype is pointer-first)
Move selection through the list.
EnterOpen / keep the selected thread; from the composer, Enter sends, Shift+Enter newline.
e / #Archive / Delete the selected conversation.
r / fReply / Forward.
/Focus search. Esc clears it / closes the filter.
RiverSync Co., Ltd. · BangkokSPEC-UX-COM · 12

15Design-system mapping

Every element resolves to a RiverSync DS v2 class, icon or token — nothing is bespoke. The inbox descends from the vanilla shell kit (rs-shell.js + riversync-ui.css); the pl-* classes are the Pipeline-local inbox layer in pipeline.css, themed entirely through the shell's CSS variables.

ElementClassDS basis
App pill (top bar)rs-portalbadge--pipeline --smCanonical app badge, Pipeline gold hue (shell-chrome.css).
Channel chiprs-badge + toneDS Badge — 2px radius, soft tint, sentence case (badge.card.html).
Relationship kind tagpl-ktag--lead/customer/partner/prospectPipeline-local status tag, DS tone palette.
Avatarrs-av rs-av-c0…c5DS avatar tile (square, initials).
Buttonsrs-btn rs-btn--primary/--ghost/--sm, rs-iconbtnDS button kit + accent press behavior.
Command buttonspl-cmd pl-cmd--primary/--danger/--iconInbox command-bar buttons over DS tokens.
Inputs / selectrs-input rs-input-wrap rs-selectDS forms; search leading icon via rs-ic-lead.
Filter menupl-mail__filtermenu pl-fm__*Pipeline-local dropdown, DS menu styling.
Modal / scrimrs-modal rs-modal--lg rs-scrim--drawerDS overlay (8px rise + scale, shadow-lg).
Alertrs-alert rs-alert--infoDS inline alert.
ToastRS.toast({title,msg,kind})DS toast — success/info/warning tones.
Iconsdata-ic="…" + RS.hydrateIcons()Proprietary icon system (the G geometry table) — never a third-party set.

Icons in use: inbox · edit · send · archive · shield · trash · search · filter · paperclip · image · file-text · download · email · phone · globe · line · instagram · facebook · linkedin · user · activity · check · refresh · corner-down-left · corner-down-right · forward-all · more-horizontal · x · trend-up · layers · hub · info. Any new glyph is added to the DS G table first, never drawn inline.

RiverSync Co., Ltd. · BangkokSPEC-UX-COM · 13

16As-built vs production targets

The prototype is the source of truth for look & behavior; these are the deltas a production build must close. None changes the model.

AreaAs-builtProduction target
List widthfluid ~38%Fixed 320px list, reader fills remainder.
Realtimestatic datasetLive SignalR hub + optimistic reconcile (§12).
Folder movestoast onlyActually move the conversation & emit conversation.foldered.
Compose / replytoast onlyPersist Message, write Activity, emit message.sent.
Attachmentschips onlyReal upload (staged chips + progress), image lightbox, file download.
Keyboardpointer-firstFull keyboard model (§14).
Responsivetwo-pane<900px master-detail with back affordance.

17Open questions

18Traceability

This specTraces to
Whole documentPIP-15 (Pipeline PRD) realizing SAL-9 (platform).
Data model (§4)Conversation · Message · DM-53 · DM-54 (SPEC-ERD-PIP).
Realtime (§12)message.received · message.sent · conversation.foldered · conversation.forwarded · SVC-18 (SPEC-DDD-SAL).
Process (§11, §13)SPEC-PWF-COM · Conversation lifecycle in SPEC-DWF-SAL.
Activity (§13)PIP-14 · DM-51 · DM-52 (blended timeline).

19Revision history

VersionDateChanges
1.028 Jun 2026First issue — the comprehensive Communications design specification: scope & distinctions (§1–2), information architecture & routing (§3), the Conversation/Message data the UI renders (§4), layout & grid with annotated wireframe (§5), command bar (§6), list panel — search/filter/row anatomy (§7), reader & composer (§8), compose modal (§9), the seven channels (§10), interactions & states incl. delivery states (§11), the SignalR realtime contract with optimistic reconcile (§12), activity logging & cross-links (§13), accessibility/keyboard/motion (§14), design-system mapping (§15), as-built vs target, open questions, traceability (§16–18). A presentation view over a settled model — no new entities, fields, events or rules; cross-referenced from SPEC-APP-PIP. New Design specs doc family (SPEC-UX).
RiverSync Co., Ltd. · BangkokSPEC-UX-COM · 14